/*
 * Copyright (c) 1999-2003, Carnegie Mellon University. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Products derived from the software may not be called "iVProg",
 *    nor may "iVProg" appear in their name, without prior written
 *    permission of Carnegie Mellon University.
 *
 * 4. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    "This product includes software developed by Carnegie Mellon University"
 */

package edu.cmu.cs.stage3.alice.core;

import edu.cmu.cs.stage3.alice.core.property.ObjectArrayProperty;
import edu.cmu.cs.stage3.alice.core.reference.DefaultReferenceGenerator;
import edu.cmu.cs.stage3.alice.core.reference.DefaultReferenceResolver;
import edu.cmu.cs.stage3.alice.core.reference.ObjectArrayPropertyReference;
import edu.cmu.cs.stage3.alice.core.reference.PropertyReference;
import edu.cmu.cs.stage3.progress.ProgressObserver;
import edu.cmu.cs.stage3.util.Criterion;
import edu.cmu.cs.stage3.util.HowMuch;

class CopyReferenceGenerator extends DefaultReferenceGenerator {

  private Class[] m_classesToShare;
  public CopyReferenceGenerator (Element internalRoot, Class[] classesToShare) {
    super( internalRoot );
    m_classesToShare = classesToShare;
  }

  //Rem @Override
  protected boolean isExternal (Element element) {
    if (element.isAssignableToOneOf( m_classesToShare )) {
      return true;
    }
    return super.isExternal( element );
  }
}

class VariableCriterion implements Criterion {
  private Criterion m_wrappedCriterion;
  private String m_name;
  public VariableCriterion( String name, Criterion wrappedCriterion) {
    m_name = name;
    m_wrappedCriterion = wrappedCriterion;
  }
  public String getName() {
    return m_name;
  }
  public Criterion getWrappedCriterion() {
    return m_wrappedCriterion;
  }
  public boolean accept (Object o) {
    if (o instanceof Variable ) {
      Variable variable = (Variable)o;
      if (m_name != null) {
        return m_name.equalsIgnoreCase( variable.name.getStringValue() );
      } else {
        return false;
      }
    } else {
      return false;
    }
  }
  //Rem @Override
  public String toString() {
    return "VariableCriterion["+m_name+"]";
  }
}

class CodeCopyReferenceGenerator extends CopyReferenceGenerator {
  public CodeCopyReferenceGenerator (Element internalRoot, Class[] classesToShare) {
    super( internalRoot, classesToShare );
  }

  //Rem @Override
  public Criterion generateReference (edu.cmu.cs.stage3.alice.core.Element element) {
    Criterion criterion = super.generateReference( element ); 
    if (element instanceof Variable) {
      Element parent = element.getParent();
      if (parent instanceof edu.cmu.cs.stage3.alice.core.Sandbox) {
        //pass
      } else {
        criterion = new VariableCriterion( element.name.getStringValue(), criterion );
      }
    }
    return criterion;
  }
}

public class CopyFactory {

  private class ElementCapsule {
    private class PropertyCapsule {
      private String m_name;
      private Object m_value;
      private PropertyCapsule (Property property, ReferenceGenerator referenceGenerator) {
        m_name = property.getName();
        if (property instanceof ObjectArrayProperty) {
          ObjectArrayProperty oap = (ObjectArrayProperty)property;
          if (oap.get() != null) {
            Object[] array = new Object[ oap.size() ];
            for( int i=0; i<oap.size(); i++) {
              array[ i ] = getValueToTuckAway( oap.get( i ), referenceGenerator );
            }
            m_value = array;
          } else {
            m_value = null;
          }
        } else {
          m_value = getValueToTuckAway( property.get(), referenceGenerator );
        }
      }
      private Object getCopyIfPossible (Object o) {
        if (o instanceof Cloneable) {
          // System.out.println("CopyFactory.getCopyIfPossible: "+o);
          //todo
          //return o.clone();
          // check for constructor that takes object of this class, too
          return o;
        } else {
          return o;
        }
      }
      private Object getValueToTuckAway (Object value, ReferenceGenerator referenceGenerator) {
        if (value instanceof Element) {
          return referenceGenerator.generateReference( (Element)value );
        } else {
          return getCopyIfPossible( value );
        }
      }
      private Object getValueForCopy (Property property, Object value, java.util.Vector referencesToBeResolved) {
        if (value instanceof Criterion) {
          referencesToBeResolved.addElement( new PropertyReference( property, (Criterion)value ) );
          return null;
        } else {
          return getCopyIfPossible( value );
        }
      }
      private Object getValueForCopy (ObjectArrayProperty oap, Object value, int i, java.util.Vector referencesToBeResolved) {
        if (value instanceof Criterion) {
          referencesToBeResolved.addElement( new ObjectArrayPropertyReference( oap, (Criterion)value, i, 0 ) );
          return null;
        } else {
          return getCopyIfPossible( value );
        }
      }

      public void set (Element element, java.util.Vector referencesToBeResolved) {
        Property property = element.getPropertyNamed( m_name );
        if (property instanceof ObjectArrayProperty) {
          ObjectArrayProperty oap = (ObjectArrayProperty)property;
          if (m_value != null) {
            Object[] src = (Object[])m_value;
            Object[] dst = (Object[])java.lang.reflect.Array.newInstance( oap.getComponentType(), src.length );
            for( int i=0; i<src.length; i++) {
              dst[ i ] = getValueForCopy( oap, src[ i ], i, referencesToBeResolved );
            }
            oap.set( dst );
          } else {
            oap.set( null );
          }
        } else {
          property.set( getValueForCopy( property, m_value, referencesToBeResolved ) );
        }
      }
    }

    private Class m_cls;
    private PropertyCapsule[] m_propertyCapsules;
    private ElementCapsule[] m_childCapsules;

    private ElementCapsule (Element element, ReferenceGenerator referenceGenerator, Class[] classesToShare, HowMuch howMuch) {
      m_cls = element.getClass();

      //todo: handle howMuch
      Element[] elementChildren = element.getChildren();
      m_childCapsules = new ElementCapsule[ elementChildren.length ];
      for( int i=0; i<m_childCapsules.length; i++) {
        if (elementChildren[ i ].isAssignableToOneOf( classesToShare )) {
          m_childCapsules[ i ] = null;
        } else {
          m_childCapsules[ i ] = new ElementCapsule( elementChildren[ i ], referenceGenerator, classesToShare, howMuch );
        }
      }

      Property[] elementProperties = element.getProperties();
      m_propertyCapsules = new PropertyCapsule[ elementProperties.length ];
      for( int i=0; i<m_propertyCapsules.length; i++) {
        m_propertyCapsules[ i ] = new PropertyCapsule( elementProperties[ i ], referenceGenerator );
      }
    }

    private Element internalManufacture (java.util.Vector referencesToBeResolved) {
      Element element;
      try {
        element = (Element)m_cls.newInstance();
      } catch( Throwable t) {
        throw new RuntimeException();
        }
      //LOB for (ElementCapsule capsule : m_childCapsules)
      int t_tam = m_childCapsules!=null ? m_childCapsules.length : 0;
      for (int i_i=0; i_i<t_tam; i_i++) {
        if (m_childCapsules[i_i] != null) { //L capsule
           element.addChild( m_childCapsules[i_i].internalManufacture( referencesToBeResolved ) ); //L capsule.internalManufacture( referencesToBeResolved )
           }
        }
      //L for (PropertyCapsule capsule : m_propertyCapsules)
      t_tam = m_propertyCapsules!=null ? m_propertyCapsules.length : 0;
      for (int i_i=0; i_i<t_tam; i_i++) {
        m_propertyCapsules[i_i].set( element, referencesToBeResolved ); //L capsule.set( element, referencesToBeResolved )
        }
      return element;
      }

    private Element lookup (Element element, VariableCriterion variableCriterion) {
      if (element != null) {
        for( int i=0; i<element.getChildCount(); i++) {
          Element child = element.getChildAt( i );
          if (variableCriterion.accept( child )) {
            return child;
          }
        }
        return lookup( element.getParent(), variableCriterion );       
      } else {
        return null;
      }
    }

    //todo: update progressObserver
    private Element manufacture (ReferenceResolver referenceResolver, ProgressObserver progressObserver, Element parentToBe)
            throws UnresolvablePropertyReferencesException {
      java.util.Vector referencesToBeResolved = new java.util.Vector();
      Element element = internalManufacture( referencesToBeResolved );
      java.util.Vector referencesLeftUnresolved = new java.util.Vector();
      element.setParent( parentToBe );
      try {
        if (referenceResolver instanceof DefaultReferenceResolver) {
          DefaultReferenceResolver drr = (DefaultReferenceResolver)referenceResolver;
          if (drr.getInternalRoot() == null) {
            drr.setInternalRoot( element );
            }
          }
        java.util.Enumeration enu = referencesToBeResolved.elements();
        while( enu.hasMoreElements()) {
          PropertyReference propertyReference = ((PropertyReference)enu.nextElement());
          try {
            Criterion criterion = propertyReference.getCriterion();
            if (criterion instanceof VariableCriterion) {
              VariableCriterion variableCriterion = (VariableCriterion)criterion;
              Element variable = lookup( propertyReference.getProperty().getOwner(), variableCriterion );
              if (variable != null) {
                propertyReference.getProperty().set( variable );
              } else {
                throw new edu.cmu.cs.stage3.alice.core.UnresolvableReferenceException( variableCriterion, "could not resolve variable criterion: " + variableCriterion );
                }
            } else {
              propertyReference.resolve( referenceResolver );
              }
          } catch( UnresolvableReferenceException ure) {
            referencesLeftUnresolved.addElement( propertyReference );
            }
          }
      } finally {
        element.setParent( null );
        }
      PropertyReference [] propertyReferences = null;
      try {
       if (referencesLeftUnresolved.size() > 0) {
         //L PropertyReference[] propertyReferences = new PropertyReference[ referencesLeftUnresolved.size() ];
         propertyReferences = new PropertyReference[ referencesLeftUnresolved.size() ];
         referencesLeftUnresolved.copyInto( propertyReferences );
         StringBuffer sb = new StringBuffer();
         sb.append( "PropertyReferences: \n" );
         //LOB for (PropertyReference propertyReference : propertyReferences)
         PropertyReference propertyReference = null; //L
         int t_tam = propertyReferences!=null ? propertyReferences.length : 0;
         for (int i_i=0; i_i<t_tam; i_i++) {
           propertyReference = propertyReferences[i_i]; //L
           sb.append( propertyReference );
           sb.append( "\n" );
           }
         //L throw new UnresolvablePropertyReferencesException( propertyReferences, element, sb.toString() );
         }
      } catch (Exception e) {
         System.err.println("CopyFactory.java: manufacture(...): propertyReferences="+edu.cmu.cs.stage3.util.StrUtilities.getComponentName(propertyReferences)+
                            edu.cmu.cs.stage3.util.StrUtilities.getComponentName(element)+": "+e.toString()); 
         e.printStackTrace();
         }
      return element;
      }
    } // Element manufacture(ReferenceResolver referenceResolver, ProgressObserver progressObserver, Element parentToBe)
  // public class CopyFactory 

  private ElementCapsule m_capsule;
  private Class m_valueClass;
  private Class HACK_m_hackValueClass;

  public CopyFactory (Element element, Element internalReferenceRoot, Class[] classesToShare, HowMuch howMuch) {
    ReferenceGenerator referenceGenerator;
    if (element instanceof Response || element instanceof edu.cmu.cs.stage3.alice.core.question.userdefined.Component) {
      referenceGenerator = new CodeCopyReferenceGenerator( internalReferenceRoot, classesToShare );
    } else {
      referenceGenerator = new CopyReferenceGenerator( internalReferenceRoot, classesToShare );
    }
    m_capsule = new ElementCapsule( element, referenceGenerator, classesToShare, howMuch );
    m_valueClass = element.getClass();
    HACK_m_hackValueClass = null;
    try {
      if (element instanceof Expression) {
        HACK_m_hackValueClass = ((Expression)element).getValueClass();
      }
    } catch( Throwable t) {
      //pass
    }
  }

  public Class getValueClass () {
    return m_valueClass;
  }
  public Class HACK_getExpressionValueClass () {
    return HACK_m_hackValueClass;
  }

  public Element manufactureCopy (ReferenceResolver referenceResolver, ProgressObserver progressObserver, Element parentToBe ) throws UnresolvablePropertyReferencesException {
    return m_capsule.manufacture( referenceResolver, progressObserver, parentToBe );
  }
  public Element manufactureCopy (Element externalRoot, Element internalRoot, ProgressObserver progressObserver, Element parentToBe ) throws UnresolvablePropertyReferencesException {
    return manufactureCopy( new DefaultReferenceResolver( internalRoot, externalRoot ), progressObserver, parentToBe );
  }
  public Element manufactureCopy (Element externalRoot, Element internalRoot, ProgressObserver progressObserver ) throws UnresolvablePropertyReferencesException {
    return manufactureCopy( externalRoot, internalRoot, progressObserver, null );
  }
  public Element manufactureCopy (Element externalRoot, Element internalRoot ) throws UnresolvablePropertyReferencesException {
    return manufactureCopy( externalRoot, internalRoot, null );
  }
  public Element manufactureCopy (Element externalRoot ) throws UnresolvablePropertyReferencesException {
    return manufactureCopy( externalRoot, (Element)null );
  }

  //Rem @Override
  public String toString() {
    return "edu.cmu.cs.stage3.alice.core.CopyFactory[" + m_valueClass + "]";
  }

}
